/**
 * Copyright 2009-2017 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ibatis.reflection;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 参数名解析器
 * <p>
 * mapper接口解析
 */
public class ParamNameResolver {

    private static final String GENERIC_NAME_PREFIX = "param";

    /**
     * <p>
     * The key is the index and the value is the name of the parameter.<br />
     * The name is obtained from {@link Param} if specified. When {@link Param} is not specified,
     * the parameter index is used. Note that this index could be different from the actual index
     * when the method has special parameters (i.e. {@link RowBounds} or {@link ResultHandler}).
     * </p>
     * <ul>
     * <li>aMethod(@Param("M") int a, @Param("N") int b) -&gt; {{0, "M"}, {1, "N"}}</li>
     * <li>aMethod(int a, int b) -&gt; {{0, "0"}, {1, "1"}}</li>
     * <li>aMethod(int a, RowBounds rb, int b) -&gt; {{0, "0"}, {2, "1"}}</li>
     * </ul>
     *   参数名映射
     * <p>
     *   KEY：参数顺序
     *   VALUE：参数名
     */
    private final SortedMap<Integer, String> names;

    /**
     * 是否有 {@link Param} 注解的参数
     */
    private boolean hasParamAnnotation;

    public ParamNameResolver(Configuration config, Method method) {
        //方法参数列表
        final Class<?>[] paramTypes = method.getParameterTypes();
        //参数的注解信息
        final Annotation[][] paramAnnotations = method.getParameterAnnotations();
        final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
        int paramCount = paramAnnotations.length;
        for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
            /*特殊参数忽略*/
            if (isSpecialParameter(paramTypes[paramIndex])) {
                continue;
            }
            String name = null;
            // 首先，从 @Param 注解中获取参数
            for (Annotation annotation : paramAnnotations[paramIndex]) {
                if (annotation instanceof Param) {
                    hasParamAnnotation = true;
                    //如果参数注解了@Param，获取注解的value属性值
                    name = ((Param) annotation).value();
                    break;
                }
            }
            if (name == null) {
                //默认为true，可以配置  获取真实的参数名
                if (config.isUseActualParamName()) {
                    //反射获取参数的真实名称
                    name = getActualParamName(method, paramIndex);
                }
                // 最差，使用 map 的顺序，作为编号
                if (name == null) {
                    //如果没有办法加载java.lang.reflect.Parameter字节码，name就可能为null，这时用参数的下标作为name
                    name = String.valueOf(map.size());
                }
            }
            map.put(paramIndex, name); //下标和name做映射
        }
        //保证顺序 构建不可变集合
        names = Collections.unmodifiableSortedMap(map);
    }

    private String getActualParamName(Method method, int paramIndex) {
        if (Jdk.parameterExists) {
            return ParamNameUtil.getParamNames(method).get(paramIndex);
        }
        return null;
    }

    private static boolean isSpecialParameter(Class<?> clazz) {
        return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
    }

    /**
     * Returns parameter names referenced by SQL providers.
     */
    public String[] getNames() {
        return names.values().toArray(new String[0]);
    }

    /**
     * <p>
     * A single non-special parameter is returned without a name.<br />
     * Multiple parameters are named using the naming rule.<br />
     * In addition to the default names, this method also adds the generic names (param1, param2,
     * ...).
     * </p>
     * 获得参数名与值的映射
     */
    public Object getNamedParams(Object[] args) {
        final int paramCount = names.size();
        if (args == null || paramCount == 0) {
            //解析的参数名称为空，返回null
            return null;
        } else if (!hasParamAnnotation && paramCount == 1) {
            //没有注解@Param的参数并且解析的参数名称只有一个，根据下标返回参数。只有一个非注解的参数，直接返回首元素
            return args[names.firstKey()];
        } else {
            // 集合。
            // 组合 1 ：KEY：参数名，VALUE：参数值
            // 组合 2 ：KEY：GENERIC_NAME_PREFIX + 参数顺序，VALUE ：参数值
            final Map<String, Object> param = new ParamMap<Object>();
            int i = 0;
            for (Map.Entry<Integer, String> entry : names.entrySet()) {
                //添加参数命名和参数值的对应关系 组合 1 ：添加到 param 中
                param.put(entry.getValue(), args[entry.getKey()]);
                //通用的参数名称，param1，param2...
                final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
                // ensure not to overwrite parameter named with @Param
                if (!names.containsValue(genericParamName)) {
                    //添加通用参数名称和参数值的对应关系
                    param.put(genericParamName, args[entry.getKey()]);
                }
                i++;
            }
            return param;
        }
    }
}
